2

还是用的vue,本来以为不合适,但想法错了。贪吃蛇并不是通过操作dom来完成移动的,而是通过记录贪吃蛇的路径来将身体渲染出来。

一般移动元素,我们都是变动它的css达到目的,但我在写贪吃蛇的时候发现这样很难以实现,参考了网上的资源,发现大部分人是通过记录贪吃蛇的路径,保存进数组,通过数组变动来表示贪吃蛇的下一步,主要是增加头部位置,去除尾部位置,再动态添加css样式,这样就达到移动的效果。

演示

链接描述

html&&data:

<div id="snake">
        <table>
        <tr v-for="(col,y) in cols"><td v-for="(row,x) in rows" :class="body(x,y)||showfood(x,y)?'active':''"></td></tr>
        </table>
        <button @click="start()">开始</button>
</div>

data(){
    return{
        rows:'',//横框
        cols:'',//竖框
        position:[[0,0],[1,0],[2,0],[3,0]],//蛇的初始位置
        direction:1,//方向
        food:[]//食物的位置
    }
},

初始化准备

methods:{
    background(){//生成横框和竖框的函数
        this.rows=Array(30)
        this.cols=Array(30)
    }, 
    keyboard(){//键盘事件
        let _this = this;
        document.onkeydown = function(e){
            if(e.keyCode===37){
                _this.change(-1)
            }else if(e.keyCode===38){
                _this.change(-2)
            }else if(e.keyCode===39){
                _this.change(1)
            }else{
                _this.change(2)
            }
        }
    },
    creatfood(){//创造食物
        this.food[0]=Math.floor(Math.random()*30)
        this.food[1]=Math.floor(Math.random()*30)
    },
    showfood(x,y){//显示食物
        if(this.food[0]===x&&this.food[1]===y){
            return true
        }
    },
    body(x,y){//显示身体
        for(i=0;i<this.position.length;i++){//循环身体函数,利用索引和身体位置做毕竟,如果索引和身体数组重合就会添加active样式
            if(this.position[i][0]===x&&this.position[i][1]===y){
                return true
            }
        }
    },

前期准备就是这么多,接下来就是跑起来,先声明一个计时器

let timer=''

接着就用定时器开始跑

start(){//开始按钮
    timer=setInterval(()=>this.autorun(),300)
}

这里的autorun就是我们要写的跑动函数

autorun(){                    
    let direction=this.direction//目前方向
    let headX,headY//
        headX=this.position[this.position.length-1][0]//复制蛇头的X坐标
        headY=this.position[this.position.length-1][1]//复制蛇头的Y坐标
        if(direction===1||direction===-1){//如果方向是在左右跑动                        
            direction>0?headX++:headX--//往右跑X坐标+1,往左跑X坐标-1                                        
        }else{
            direction>0?headY++:headY--//如果方向是在上下跑动,Y坐标做对应处理
        }
//此时蛇头的下一个坐标位置就是[headX,headY],接下来就可以判断是否结束游戏,如果结束了,蛇头就没必要添加了
        if(headX<0||headX>29||headY<0||headY>29||this.body(headX,headY)){//当蛇头下一个位置出了边界或者这个位置是符合身体函数(即蛇头撞上了身体)
            alert('Game Over')//结束
            clearInterval(timer)//清除定时器
            this.position=[[0,0],[1,0],[2,0],[3,0]]//还原身体
            this.creatfood()//重新创造食物
            this.direction=1//还原方向
        }else{//如果蛇头下一个位置是符合规则的                        
            this.position.push([headX,headY])//将下一个位置添加进数组,头部长一节
            if(headX!==this.food[0]||headY!==this.food[1]){//如果下一个头部位置不是食物的位置,即吃食物开始                            
                this.position.shift()//我们将尾部去掉,一长一短实现了蛇的走动
            }else{//如果下一个头部位置是食物
                this.creatfood()//不去除尾部,再次创建食物(这里有个小bug,随机的食物有几率与身体重合)
            }
        }                                    
},
change(dir){//改变方向
    if(Math.abs(dir)===Math.abs(this.direction)){//如果方向相同或者想法,不做任何操作
        return
    }else{
        this.direction=dir//否则把方向改动
    }
},

就是这个样子,贪吃蛇就写完了,逻辑方面并不是太复杂,但是对于数组的操作有很多,这里提下我碰见的几个问题:

  • vue中的数组对象在更新的时候和单独的数据是不一样的,只有某些特定的函数会去主动更新数组,否则的话需要用vm.$set( target, key, value )这个去更新数组
  • 二维数组和一维数组是不同的,indexOf()不管用,无法检测;而且类似this.position[0]===[0,0]也是无法正确判断的,这导致我在写这个小demo的时候犯了很多错。
let po=[[0,0],[1,0],[2,0],[3,0]]
let qo=[[0,0]]
let oo=[0,0]
console.log(po[0]==qo[0])//false
console.log(qo[0]==oo)//false

这大概就是最大的收获,我还太年轻。
因为JavaScript里面Array是对象,==或===操作符只能比较两个对象是否是同一个实例,也就是是否是同一个对象引用。目前JavaScript没有内置的操作符判断对象的内容是否相同。
但是惯性思维让人以为数组也是值,是可以比较的。(这段是复制的,别人总结的,和我想法一模一样)希望能给同为小白的朋友们提个醒

源码

https://github.com/yuyeqianxu...
希望能帮助到和我一样的小白朋友们,有bug麻烦反馈,谢谢!


羽叶千寻
133 声望6 粉丝